﻿using Core.Tools;
using System;

namespace Core.ObjectOperate.ReflectionImpl
{
    public abstract class FieldInfo
    {
        public static FieldInfo Create(System.Reflection.FieldInfo FieldInfo)
        {
            if (FieldInfo == null)
            {
                throw new NullReferenceException("FieldInfo");
            }

            var Result = new FieldInfoImpl<object>(FieldInfo);

            var ResultType = TypeHelper.MakeGenericType(typeof(FieldInfoImpl<>), FieldInfo.FieldType);

            Pointer.Cast(Result, ResultType);

            return Result;
        }

        private readonly bool InternalIsPublic;
        private readonly int InternalOffset;
        private readonly string InternalName;
        private readonly bool InternalDeclaringTypeIsClass;

        public unsafe FieldInfo(System.Reflection.FieldInfo FieldInfo)
        {
            InternalName = FieldInfo.Name;
            InternalOffset = TypeHelper.OffsetOf(FieldInfo);
            InternalIsPublic = FieldInfo.IsPublic;
            InternalDeclaringTypeIsClass = FieldInfo.DeclaringType.IsClass;
        }

        private unsafe IntPtr GetObjectContentPointer(TypedReference TypedRef)
        {
            var Result = Pointer.UnBox(TypedRef);

            if (InternalDeclaringTypeIsClass)
            {
                Result = *(IntPtr*)Result + sizeof(IntPtr);
            }

            return Result;
        }

        private unsafe IntPtr GetObjectContentPointer(object Object)
        {
            return Pointer.UnBox(Object) + sizeof(IntPtr);
        }
        
        public bool IsPublic { get { return InternalIsPublic; } }

        public string Name { get { return InternalName; } }

        public abstract Type Type { get; }

        public abstract object GetDirectValue(TypedReference TypedRef);

        public abstract void SetDirectValue(TypedReference TypedRef, object Value);

        public abstract object GetValue(object Object);

        public abstract void SetValue(object Object, object Value);

        public sealed class FieldInfoImpl<TField> : FieldInfo
        {
            public override Type Type { get { return typeof(TField); } }

            public FieldInfoImpl(System.Reflection.FieldInfo FieldInfo) : base(FieldInfo)
            {
            }

            public TField GetDirectValueImpl(TypedReference TypedRef)
            {
                return Pointer.GetValue<TField>(GetObjectContentPointer(TypedRef) + InternalOffset);
            }

            public void SetDirectValueImpl(TypedReference TypedRef, TField Value)
            {
                Pointer.SetValue(GetObjectContentPointer(TypedRef) + InternalOffset, Value);
            }
            public TField GetValueImpl(object Object)
            {
                return Pointer.GetValue<TField>(GetObjectContentPointer(Object) + InternalOffset);
            }

            public void SetValueImpl(object Object, TField Value)
            {
                Pointer.SetValue(GetObjectContentPointer(Object) + InternalOffset, Value);
            }

            public override object GetDirectValue(TypedReference TypedRef)
            {
                return GetDirectValueImpl(TypedRef);
            }

            public override void SetDirectValue(TypedReference TypedRef, object Value)
            {
                SetDirectValueImpl(TypedRef, (TField)Value);
            }

            public override object GetValue(object Object)
            {
                return GetValueImpl(Object);
            }

            public override void SetValue(object Object, object Value)
            {
                SetValueImpl(Object, (TField)Value);
            }
        }
    }
}